﻿using System;
using mshtml;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Reflection;
using AutoPageHelper;

namespace AutoIE
{
    public class Page : AutoIE.IPage
    {
        private IHTMLDocument _doc = null;
        private string _url = "";
        public IHTMLDocument Document
        {
            set { _doc = value; }
            get { return _doc as IHTMLDocument; }
        }
        public IHTMLDocument2 Document2
        {
            get { return _doc as IHTMLDocument2; }
        }
        public IHTMLDocument3 Document3
        {
            get { return _doc as IHTMLDocument3; }
        }
        public string Url
        {
            get { return _url;}
        }
        public string DocUrl
        {
            get { return Document2.url; }
        }
        public string DocTitle
        {
            get { return Document2.title; }
        }
        public string State
        {
            get { return Document2.readyState; }
        }
        public bool IsChanged
        {
            get { return !((Url.Equals(DocUrl, StringComparison.InvariantCultureIgnoreCase)) && (State == "complete")); }
        }
        public Page()
        {

        }
        public Page(IHTMLDocument doc, string url)
            : this()
        {
            Document = doc;
            _url = url;
            //if (_url.IndexOf("://") < 0)
            //    _url = "file://" + _url;
        }
        public Page(IHTMLDocument doc)
            : this(doc, "")
        {
            _url = (doc as IHTMLDocument2).url;
        }
        public Page(string url)
            : this(null, url)
        {
        }
        public void InjectFunction(string functionName, string functionCode)
        {
            DbgLn(functionName + "," + functionCode);
            string js = string.Format("window.{0}={1};", functionName, functionCode);
            ExecuteScript(js);
        }
        public void IgnorFunction(string functionName)
        {
            DbgLn(functionName);
            string code = "function(){return true;}";
            InjectFunction(functionName, code);
        }
        public bool Fill(object[] mapping)
        {
            DbgLn(string.Join(";", mapping));
            bool filled = true;
            if (State == "loading")
                return false;

            IgnorFunction("confirm");
            if (mapping == null || mapping.Length < 2)
                return filled;
            for (int i = 0; i < mapping.Length - 1; i+=2 )
            {
                string value = mapping[i].ToString();
                string elem = mapping[i+1].ToString();
                if(!SetElemValue(elem,value))
                    filled = false;
            }
            DbgLnOk("", filled.ToString());
            return filled;
        }
        private bool SetElemValue(string elemName, string val)
        {
            if (_doc == null)
                return false;

            if (string.IsNullOrEmpty(elemName))
                return true;
            object obj = Document2.all.item(elemName.Trim());
            if (obj == null)
                return false;

            IHTMLElement elem = null;
            IHTMLElementCollection elems = null;
            if (obj is IHTMLElementCollection)
            {
                elems = obj as IHTMLElementCollection;
                if (elems != null && elems.length > 0)
                    elem = elems.item(0) as IHTMLElement;
            }
            else
            {
                elem = obj as IHTMLElement;
            }

            if (elem == null)
                return false;


            IHTMLElement3 elem3 = elem as IHTMLElement3;
            string tag = elem.tagName.ToUpper();
            if (tag.Equals("TEXTAREA"))
            {
                HTMLTextAreaElement txtArea = elem as HTMLTextAreaElement;
                txtArea.value = val;
                txtArea.FireEvent("onblur");
                return true;
            }
            else if (tag.Equals("SELECT"))
            {
                HTMLSelectElement slct = elem as HTMLSelectElement;
                string val0 = slct.value;
                slct.value = val;
                if(!val.Equals(val0))
                    slct.FireEvent("onchange");
                return true;
            }
            else if (tag.Equals("INPUT"))
            {
                HTMLInputElement txtInput = elem as HTMLInputElement;
                string type = txtInput.type.ToLower();
                if (type.Equals("text") || type.Equals("password"))
                {
                    string val0 = txtInput.value;
                    txtInput.value = val;
                    if (!val.Equals(val0))
                        txtInput.FireEvent("onchange");
                    txtInput.FireEvent("onblur");
                }
                else if (elems != null && elems.length > 0)
                {
                    if (type.Equals("radio"))
                    {
                        for (int i = 0; i < elems.length; i++)
                        {
                            IHTMLInputElement e = elems.item(i) as IHTMLInputElement;
                            string val0 = e.value;
                            if (val.Equals(val0))
                            {
                                (e as IHTMLElement).setAttribute("checked", true);
                                (e as IHTMLElement3).FireEvent("onclick");
                                return true;
                            }
                        }
                    }
                    else if (type.Equals("checkbox")) 
                    {
                        if (elems.length == 1) // 只有一个checkbox
                        {
                            bool chk = !val.StartsWith("--");
                            (elem as IHTMLElement).setAttribute("checked", chk);
                            (elem as IHTMLElement3).FireEvent("onclick");
                            return true;
                        }
                        else // 多个，用值选中，多个值用',', '|', ';'隔开， 如 --val1;--val2;val3;val4
                        {
                            String[] vals = val.Split(new char[] { ',', '|', ';' }, StringSplitOptions.RemoveEmptyEntries);
                            bool[] chk = new bool[vals.Length];
                            for (int j = 0; j < vals.Length; j++)
                            {
                                vals[j] = vals[j].Trim();
                                if (vals[j].StartsWith("--"))
                                {
                                    chk[j] = false;
                                    vals[j] = vals[j].Substring(2);
                                }
                                else
                                    chk[j] = true;
                            }
                            for (int i = 0; i < elems.length; i++)
                            {
                                IHTMLInputElement e = elems.item(i) as IHTMLInputElement;
                                string val0 = e.value;
                                for (int j = 0; j < vals.Length; j++)
                                {
                                    if (val0.Equals(vals[j]))
                                    {
                                        (e as IHTMLElement).setAttribute("checked", chk[j]);
                                        (e as IHTMLElement3).FireEvent("onclick");
                                        break;
                                    }
                                }
                            } // for i
                        }
                    }

                }
                return true;
            }
            return false;
        }

        private IHTMLElement GetElem(string elemNameId, IHTMLDocument2 doc, bool bRecursive = true)
        {
            object obj = doc.all.item(elemNameId.Trim()); // doc.getElementById(elemNameId);
            if (obj == null)
                return null;
            IHTMLElement elem = null;
            if (obj is IHTMLElementCollection)
            {
                elem = (obj as IHTMLElementCollection).item(0) as IHTMLElement;
            }
            else
            {
                elem = obj as IHTMLElement;
            }

            if (elem != null || !bRecursive)
                return elem;

            // find in iframes
            FramesCollection iframes = doc.frames;
            for (int i = 0; i < iframes.length; i++)
            {
                HTMLIFrame ifrm = iframes.item(i) as HTMLIFrame;
                IHTMLDocument2 docx = ifrm.contentWindow.document;
                elem = GetElem(elemNameId, docx, false);
                if (elem != null)
                    return elem;
            }

            // find in parent frames
            FramesCollection frames = doc.parentWindow.frames;
            for (int i = 0; i < frames.length; i++)
            {
                HTMLIFrame frm = frames.item(i) as HTMLIFrame;
                IHTMLDocument2 docx = frm.contentWindow.document;
                elem = GetElem(elemNameId, docx, false);
                if (elem != null)
                    return elem;
            }
            return elem;
        }
        private IHTMLElement GetElemByOuterHTML(string html)
        {
            int indexg = html.IndexOf('>') + 1;
            if (indexg < 1)
                return null;

            IHTMLElement e0 = Document2.createElement(html);
            if(e0 == null)
                return null;
            

            if (indexg < html.Length - 1)
            {
                int indexl = html.IndexOf('<', indexg) - 1;
                if (indexl > indexg)
                {
                    string innerHtml = html.Substring(indexg, indexl - indexg + 1);
                    e0.innerHTML = innerHtml;
                }
            }
            string outerHtml = e0.outerHTML;
            IHTMLElementCollection elems = Document3.getElementsByTagName(e0.tagName);
            foreach (IHTMLElement e in elems)
            {
                if (e.outerHTML.Equals(outerHtml, StringComparison.CurrentCultureIgnoreCase))
                    return e;
            }
            return null;
        }

        private IHTMLElement GetElemByText(string text)
        {
            IHTMLElementCollection elems = Document2.all;
            if (elems == null || string.IsNullOrEmpty(text))
                return null;

            foreach (IHTMLElement e in elems)
            {
                if (text.Equals(e.innerText))
                {
                    return e;
                }
            }
            return null;
        }

        private IHTMLElement GetElemByRegexHTML(string html)
        {
            html = html.Substring(1, html.Length - 2);
            int indexg = html.IndexOf('>') + 1;
            if (indexg < 1)
                return null;

            IHTMLElement e0 = Document2.createElement(html);
            if (e0 == null)
                return null;

            if (indexg < html.Length - 1)
            {
                int indexl = html.IndexOf('<', indexg) - 1;
                if (indexl > indexg)
                {
                    string innerHtml = html.Substring(indexg, indexl - indexg + 1);
                    e0.innerHTML = innerHtml;
                }
            }

            Regex reg = MyUtility.GetRegex("^" + e0.outerHTML + "$");
            IHTMLElementCollection elems = Document3.getElementsByTagName(e0.tagName);
            foreach (IHTMLElement e in elems)
            {
                if (reg.IsMatch(e.outerHTML))
                    return e;
            }
            return null;
        }

        private IHTMLElement GetElem(string pattern)
        {
            if (string.IsNullOrEmpty(pattern))
                return null;
            IHTMLElement elem = null;
            pattern = pattern.Trim();
            if (pattern.StartsWith("<") && pattern.EndsWith(">")) // HTML代码
            {
                elem = GetElemByOuterHTML(pattern);
            }
            else if (pattern.StartsWith("^<") && pattern.EndsWith(">$")) // 正则表达式
            {
                elem = GetElemByRegexHTML(pattern);
            }
            else if (pattern.Contains(";") || pattern.Contains(",")) // ID模式列表
            {
                string[] btns = pattern.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
                foreach(string id in btns)
                {
                    elem = GetElem(id);
                    if (elem != null)
                        break;
                }
            }
            else // 单个ID或名字
            {
                object obj = Document2.all.item(pattern.Trim()); // doc.getElementById(elemNameId);
                if (obj == null)
                    return null;
                
                if (obj is IHTMLElementCollection)
                {
                    elem = (obj as IHTMLElementCollection).item(0) as IHTMLElement;
                }
                else
                {
                    elem = obj as IHTMLElement;
                }
            }
            return elem;
        }
        public string GetElemUniqueID(string pattern)
        {
            IHTMLElement elem = GetElem(pattern);
            if (elem != null)
                return (elem as IHTMLUniqueName).uniqueID;
            else
                return "";
        }
        public string GetElemValue(string elemId)
        {
            IHTMLElement elem = GetElem(elemId);
            string v = "";
            if (elem != null)
                v = elem.getAttribute("value") as string;
            return v;
        }
        public string GetElemText(string elemId)
        {
            IHTMLElement elem = GetElem(elemId);
            if (elem == null)
                return "";
            string v = elem.innerText;
            return v;
        }
        public string GetElemHtml(string elemId)
        {
            IHTMLElement elem = GetElem(elemId);
            if (elem == null)
                return "";
            string v = elem.outerHTML;
            return v;
        }
        public Image GetImage(string imgSrcAlt)
        {
            IHTMLElementCollection imgs = Document2.images;
            if (imgs == null || imgs.length < 1)
                return null;
            IHTMLImgElement img = null;

            for (int i = 0; i < imgs.length; i++)
            {
                img = imgs.item(i) as IHTMLImgElement;
                if (img == null)
                    continue;
                string src = img.src;
                if (string.IsNullOrEmpty(src))
                    continue;
                int j = src.IndexOf('?');

                if (j > 0)
                {
                    src = src.Substring(0, j);
                }
                if (src.Equals(imgSrcAlt, StringComparison.InvariantCultureIgnoreCase))
                    break;
                if (string.IsNullOrEmpty(img.alt))
                    continue;
                if (img.alt.Equals(imgSrcAlt, StringComparison.InvariantCultureIgnoreCase))
                    break;
            }
            if (img == null)
                return null;
            IHTMLControlRange rng = (Document2.body as HTMLBody).createControlRange() as IHTMLControlRange;
            rng.add(img as IHTMLControlElement);
            MyUtility.SetClipboardAllowed();
            rng.execCommand("Copy");
            Image image = new Bitmap(Clipboard.GetImage());
            //image.Save("code.gif");
            Clipboard.Clear();
            return image;
        }
        public bool Submit()
        {
            DbgLn("");
            IHTMLElementCollection elems = Document3.getElementsByTagName("FORM");
            if (elems == null || elems.length < 1)
                return false;
            IHTMLElement3 form = (IHTMLElement3)elems.item(0);
            form.FireEvent("onsubmit");
            DbgLnOk("");
            return true;
        }
        public bool Submit(string formName)
        {
            DbgLn(formName );
            IHTMLElement form = GetElem(formName);
            if (form == null)
                return false;
            if (form.tagName.ToUpper() != "FORM")
                return false;
            (form as IHTMLElement3).FireEvent("onsubmit");
            DbgLnOk(formName);
            return true;
        }
        public bool Click(string elemId)
        {
            DbgLn(elemId);
            IHTMLElement elem = GetElem(elemId);
            if (elem == null)
                return false;
            elem.click();
            DbgLnOk(elemId);
            return true;
        }
        public void Navigate(string url)
        {
            DbgLn(url);
            if (Document2 != null && Document2.parentWindow != null) // must in a STA thread
            {
                Document2.parentWindow.navigate(url);
                DbgLnOk(url);
            }
        }

        public object ExecuteFunction(string functionName, object[] parameters = null)
        {
            DbgLn(functionName);
            if (_doc == null || string.IsNullOrEmpty(functionName))
                return null;
            bool bSuccess = false;
            object result = null;
            try
            {
                IHTMLWindow2 win = Document2.parentWindow; // must in a STA thread
                if (win != null)
                {
                    result = win.GetType().InvokeMember(functionName, System.Reflection.BindingFlags.InvokeMethod, null, win, parameters);
                    DbgLnOk(functionName , result.ToString());
                    bSuccess = true;
                }
            }
            catch (COMException) // JS语法错误
            {
                DbgLnErr("syntax error" + functionName);
            }
            catch (Exception) // 其他错误
            {
            }
            if (bSuccess)
                return result;
            return null;
        }
        public bool ExecuteScript(string js)
        {
            DbgLn(js);
            if (_doc == null || string.IsNullOrEmpty(js))
                return false;
            bool bSuccess = false;
            object result = null;
            try
            {
                IHTMLWindow2 win = Document2.parentWindow; // must in a STA thread
                if (win != null)
                {
                    result = win.execScript(js);
                    DbgLnOk(js);
                    bSuccess = true;
                }
            }
            catch (COMException) // JS语法错误
            {
                DbgLnErr("syntax error" + js);
            }
            catch (Exception) // 其他错误
            {
            }
            return bSuccess;
        }
        public bool Contains(string text)
        {
            if (Document3 == null || Document3.documentElement == null)
                return false;
            if (string.IsNullOrEmpty(Document3.documentElement.innerText))
                return false;
            return Document3.documentElement.innerText.Contains(text);
        }

        [Conditional("DEBUG")]
        void DbgLn(string text, int index = 1)
        {
            MethodInfo mi = new StackTrace().GetFrame(index).GetMethod() as MethodInfo;
            text = mi.Name + ": " + text + " @ " + DocUrl ;
            Debug.WriteLine(text);
        }

        [Conditional("DEBUG")]
        void DbgLnOk(string text, string result="")
        {
            if (string.IsNullOrEmpty(result))
                DbgLn(text + " [OK]", 2);
            else
                DbgLn(text + " -> " + result + " [OK]", 2);
        }
        [Conditional("DEBUG")]
        void DbgLnErr(string text)
        {
            DbgLn(text + " [ERROR]", 2);
        }


        

    }
}
